package com.jxtc.enterprise.employee.controller;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.jxtc.enterprise.common.constants.*;
import com.jxtc.enterprise.common.entity.*;
import com.jxtc.enterprise.common.mapper.*;
import com.jxtc.enterprise.common.utils.Result;
import com.jxtc.enterprise.common.utils.ResultCode;
import com.jxtc.enterprise.common.utils.ResultGenerator;
import com.jxtc.enterprise.employee.serviceImpl.JxOrderServiceImpl;
import com.jxtc.enterprise.employee.vo.resp.CompanyInfoRespVo;
import com.jxtc.enterprise.employee.vo.resp.CompanyInfoWithStoreListRespVo;
import com.jxtc.enterprise.employee.vo.resp.UserCanOrderFoodCompanyRespVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.text.MessageFormat;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 用户小程序， 下单 页面
 */
@RestController
@RequestMapping("/employee/orderPage")
@Slf4j
public class OrderPageController {

    @Autowired
    private DepartmentMapper departmentMapper;

    @Autowired
    private FoodMenuMapper foodMenuMapper;

    @Autowired
    private FoodMapper foodMapper;

    @Autowired
    private StoreMapper storeMapper;

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private JxOrderServiceImpl jxOrderServiceImpl;

    @Autowired
    private CompanyMapper companyMapper;

    @Autowired
    private OrderDetailMapper orderDetailMapper;

    @Autowired
    private MenuStoreConfigMapper menuStoreConfigMapper;

    @Autowired
    private UserOtherCompRelationMapper userOtherCompRelationMapper;


    @ApiResponse(responseCode = "200", description = "页面切换到【下单】页面后，通过此接口获取员工所属部门和部门取餐点图片",
            content = @Content(schema = @Schema(example = UserConstants.queryCompanyPickUpAddressInfosExample)))
    @Parameter(name = "departId", description = "员工所属部门id")
    @Operation(summary = "2.1 下单页面公司取餐地址信息", tags = {"2. 用户小程序-下单"})
    @GetMapping(value = "/queryCompanyPickUpAddressInfos", produces = {MediaType.APPLICATION_JSON_VALUE})
    Result<?> queryCompanyPickUpAddressInfos(String departId) {

        log.info("request params, departId: {}", departId);

        Department department = departmentMapper.selectById(departId);

        JSONObject resultData = new JSONObject();
        resultData.put("departId", department.getId()); // 员工所属部门id
        resultData.put("departName", department.getName()); // 员工所属部门名称
        // 员工所属部门取餐点图片列表
        resultData.put("images", Arrays.asList(JSONObject.parseArray(department.getPickupAddressImages(), String.class)));

        Result<JSONObject> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(resultData);

        log.info("response body:{}", result);
        return result;
    }

    @ApiResponse(responseCode = "200", description = "用户在【下单】页面，输入菜品名搜索添加后，返回为该用户所属公司供餐，且包含用户搜索菜品的店铺列表",
            content = @Content(schema = @Schema(example = UserConstants.queryAssociatedStoresByKeyword)))
    @Parameter(name = "mealType", description = "具体的用餐时间，如：lunch")
    @Parameter(name = "keyword", description = "模糊查询菜品名称的关键字")
    @Parameter(name = "companyId", description = "员工所属公司 ID")
    @Operation(summary = "2.2 下单页面根据搜索条件，查询为为该公司配置的，符合菜品搜索条件的店铺", tags = {"2. 用户小程序-下单"})
    @GetMapping(value = "/queryAssociatedStoresByKeyword", produces = {MediaType.APPLICATION_JSON_VALUE})
    Result<?> queryAssociatedStoresByKeyword(String companyId, String keyword, String mealType, String phone) {
        String requestId = UUID.randomUUID().toString();
        log.info("requestId {}, employee mini app view, /queryAssociatedStoresByKeyword, companyId: {}, keyword: {}, mealType: {}, phone: {}", requestId, companyId, keyword, mealType, phone);

        // 通过手机号获取用户信息
        LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<User>()
                .eq(User::getPhoneNumber, phone)
                .eq(User::getDelFlag, false)
                .orderByDesc(User::getCreateTime);
        List<User> userList = userMapper.selectList(userLambdaQueryWrapper);
        if (CollectionUtils.isEmpty(userList)) {
            log.error("requestId {}, employee mini app view, /queryAssociatedStoresByKeyword, 根据手机号查询用户信息为空，phone: {}", requestId, phone);
            throw new IllegalArgumentException("手机号为 " + phone + " 的用户不存在");
        }

        // 获取最新的用户数据
        User user = userList.get(0);

        // 判断前端传递过来的 companyId 是否存在，存在是指：
        // 1. 该 companyId 是用户所属公司，即 user.company_id
        // 2. 该 companyId 不是用户所属公司，是用户出差所在公司的 id，即 user_other_company_relation.company_id
        // 不符合上述两种情况时，直接返回错误码 604
        boolean companyExist = checkCompanyIdExistByUserPhone(user, companyId, requestId);
        if (!companyExist) {
            log.warn("requestId {}, employee mini app view, /queryAssociatedStoresByKeyword, companyId 不是用户可点餐公司 ID, phone: {}, companyId: {}", requestId, phone, companyId);
            Result<CompanyInfoWithStoreListRespVo> result = new Result<>();
            result.setCode(BusinessStatusConstants.COMPANY_ID_NOT_EXIST);
            result.setMessage("companyId 不是用户可点餐公司 ID，请重新选择下单公司");

            // 为避免出现 604 时前端需要再次调用该接口获取用户所属公司可点餐的店铺列表
            // 后端将用户所属公司信息和店铺列表直接返回共前端使用

            // 获取员工所属公司信息
            CompanyInfoRespVo companyInfo = getCompanyInfoByUser(user, requestId);
            // 获取员工所属公司的可点餐店铺列表
            List<JSONObject> storeListForCompany = queryStoreListForCompany(companyInfo.getCompanyId(), keyword, mealType, requestId);

            // 构建返回给前端的数据
            CompanyInfoWithStoreListRespVo vo = new CompanyInfoWithStoreListRespVo();
            vo.setCompanyInfo(companyInfo);
            vo.setStoreList(storeListForCompany);
            result.setData(vo);

            log.info("requestId {}, employee mini app view, /queryAssociatedStoresByKeyword, response:{}", requestId, JSONObject.toJSONString(result));
            return result;
        }

        // 根据公司 ID，模糊查询关键字以及用餐时段，查询该公司可点餐的店铺列表
        List<JSONObject> resultData = queryStoreListForCompany(companyId, keyword, mealType, requestId);

        Result<Object> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(resultData);

        return result;
    }

    private List<JSONObject> queryStoreListForCompany(String companyId, String keyword, String mealType, String requestIdForDebug) {
        // 如果前端没有传递 mealType，那么默认查询 company 表中配置的 mealsConfig 中的第一个值
        if (mealType == null || mealType.isEmpty()) {
            Company company = companyMapper.selectById(companyId);
            if (company == null) {
                log.warn("requestId {}, employee mini app view, queryStoreListForCompany, company not exist, companyId: {}", requestIdForDebug, companyId);
                throw new IllegalArgumentException("ID 为 " + companyId + " 的公司不存在");
            }
            List<MealConfig> mealsConfig = JSONObject.parseArray(company.getMealsConfig(), MealConfig.class);
            if (!mealsConfig.isEmpty()) {
                mealType = mealsConfig.get(0).getMealType();
            }
        }

        // 1. 通过菜品名进行模糊搜索，返回符合条件的菜品信息
        List<HashMap<String, Object>> matchedFoods = foodMenuMapper.queryMatchedFoodsByKeywordAndCompanyId(companyId, keyword, mealType);
        // 对返回的数据，按照 店铺id进行分组,key为店铺id，value为符合搜索条件的，该店铺中的菜品列表
        Map<String, List<HashMap<String, Object>>> matchedStoreAndFoodsMap = matchedFoods.stream().collect(Collectors.groupingBy((item) -> (String) item.get("storeId"), Collectors.toList()));
        Set<String> storeIds = matchedStoreAndFoodsMap.keySet();

        if (storeIds.isEmpty()) {
            // 如果未找到符合条件的店铺 ID 列表，返回空列表即可
            log.info("requestId {}, employee mini app view, queryStoreListForCompany, 未找到符合条件的店铺列表", requestIdForDebug);
            return Collections.emptyList();
        }

        // 2. 获取符合搜索的菜品所在店铺的所有菜品（店铺菜品中，为公司配置的所有菜品）
        List<HashMap<String, Object>> allFoodsOfStores = foodMenuMapper.queryFoodsByMatchedStoreIds(companyId, storeIds, mealType);
        // 将符合搜索的菜品所在店铺的所有菜品按照店铺id进行分组
        Map<String, List<HashMap<String, Object>>> foodOfMatchedStoreMap = allFoodsOfStores.stream().collect(Collectors.groupingBy((item) -> (String) item.get("storeId"), Collectors.toList()));

        List<JSONObject> resultData = new ArrayList<>();
        // 3. 使用上述两个map拼接最终的数据
        for (String storeId : matchedStoreAndFoodsMap.keySet()) {
            JSONObject oneStore = new JSONObject();
            oneStore.put("storeId", storeId);
            // matchedStoreAndFoodsMap 字典中，key为店铺id，value为店铺中匹配菜品名模糊搜索的菜品列表，value中任何一个数据项中都包含店铺信息，因此，取第一个就可以得到店铺信息
            // 某个店铺在 菜单上的配置，尤其时 营业开始，结束时间; 之前的变量命名，会被误以为是 店铺表中的 开始，结束 营业时间
            HashMap<String, Object> menuStoreConfig = matchedStoreAndFoodsMap.get(storeId).get(0);
            // 店铺名
            String storeName = (String) menuStoreConfig.get("storeName");
            oneStore.put("storeName", storeName);
            // 店铺logo
            String storeLogo = (String) menuStoreConfig.get("storeLogo");
            oneStore.put("storeLogo", storeLogo);
            // 店铺营业起始时间
            String storeBusinessStart = menuStoreConfig.get("startTime").toString();
            oneStore.put("storeBusinessStart", storeBusinessStart);
            // 店铺营业结束时间
            String storeBusinessEnd = menuStoreConfig.get("endTime").toString();
            oneStore.put("storeBusinessEnd", storeBusinessEnd);

            // 店铺限购份数
            Integer storeLimitCount = (Integer) menuStoreConfig.get("storeLimitCount");
            oneStore.put("storeLimitCount", storeLimitCount);
            oneStore.put("saleCount", "999+"); // 月销量
            oneStore.put("scores", "5.0"); // 店铺评分

            List<HashMap<String, Object>> foodItems = foodOfMatchedStoreMap.get(storeId);
            if (foodItems == null || foodItems.isEmpty()) {
                log.error("requestId {}, employee mini app view, queryStoreListForCompany, companyId: {}, storeId: {}, 该店铺为公司配置的菜品列表为空", requestIdForDebug, companyId, storeId);
                // 跳过这个店铺
                continue;
            }
            // 对店铺中的菜品按照 sortFlag 进行升序排序，sortFlag 值越小，排位越靠前
            foodItems.sort((o1, o2) -> {
                Integer sortFlag1 = (Integer) o1.get("foodSortFlag");
                Integer sortFlag2 = (Integer) o2.get("foodSortFlag");
                return sortFlag1 - sortFlag2;
            });
            // 只需要返回三个菜品即可，【下单】页面，各个店铺不需要展示太多菜品
            // todo 将展示的菜品个数做成配置项，可通过PC 管理员端进行配置，甚至可以做到为不同的公司配置 可以展示的菜品个数

            List<HashMap<String, Object>> matchedFoodsOfStore = foodItems.subList(0, Math.min(foodItems.size(), 3));
            // 剔除返回值中的 storeId
            matchedFoodsOfStore.forEach(item -> item.remove("storeId"));
            oneStore.put("foods", matchedFoodsOfStore);
            oneStore.put("menuId", menuStoreConfig.get("menuId"));

            resultData.add(oneStore);
        }

        return resultData;
    }

    private CompanyInfoRespVo getCompanyInfoByUser(User user, String requestIdForDebug) {
        CompanyInfoRespVo vo = new CompanyInfoRespVo();
        vo.setCompanyId(user.getCompanyId());
        vo.setDepartmentId(user.getDepartmentId());

        Company company = companyMapper.selectById(user.getCompanyId());
        if (company == null) {
            // 用户所属公司不存在时，表明存在脏数据，记录一下日志，正常流程应该不存在此情况
            log.warn("requestId {}, employee mini app view, getCompanyInfoByUser, 用户所属公司不存在, companyId: {}", requestIdForDebug, user.getCompanyId());
            return vo;
        }

        Department department = departmentMapper.selectById(user.getDepartmentId());
        if (department == null) {
            // 用户所属部门不存在时，表明存在脏数据，记录一下日志，正常流程应该不存在此情况
            log.warn("requestId {}, employee mini app view, getCompanyInfoByUser, 用户所属部门不存在, departmentId: {}", requestIdForDebug, user.getDepartmentId());
            return vo;
        }

        vo.setCompanyName(company.getName());
        vo.setDepartmentName(department.getName());
        return vo;
    }

    private boolean checkCompanyIdExistByUserPhone(User user, String companyId, String requestIdForDebug) {
        if (user.getCompanyId().equals(companyId)) {
            // 如果前端传过来的 companyId 为用户所属公司 ID，则表明前端传递过来的 companyId 时有效的
            // 校验通过
            return true;
        }

        LambdaQueryWrapper<UserOtherCompRelation> userOtherCompRelationLambdaQueryWrapper = new LambdaQueryWrapper<UserOtherCompRelation>()
                .eq(UserOtherCompRelation::getUserId, user.getId())
                .eq(UserOtherCompRelation::getOtherCompanyId, companyId)
                .eq(UserOtherCompRelation::getDelFlag, false);

        // 如果 userId + companyId 的记录在 user_other_company_relation 表中存在（且未被标记为已删除状态）
        // 则表明前端传递过来的 companyId 是有效的，返回 true
        // 否则说明该 companyId 已经不存在，返回 false
        return userOtherCompRelationMapper.exists(userOtherCompRelationLambdaQueryWrapper);
    }


    @ApiResponse(responseCode = "200", description = "用户在【下单】页面，输入菜品名搜索添加后，返回为该用户所属公司供餐，且包含用户搜索菜品的店铺列表")
    @Parameter(name = "keyword", description = "餐品名称关键字，用于根据名称模糊查询")
    @Parameter(name = "mealType", description = "具体的用餐时间（点餐时段），如：lunch")
    @Parameter(name = "storeId", description = "店铺 ID")
    @Parameter(name = "menuId", description = "菜单 ID，即为每个公司配置并启用的最新的菜单 ID")
    @Parameter(name = "companyId", description = "员工所属公司 ID")
    @Operation(summary = "2.3 查看为公司配置的菜单中配置的某个店铺的菜品详情", tags = {"2. 用户小程序-下单"})
    @GetMapping(value = "/queryStoreDetailsOfCompanyMenu", produces = {MediaType.APPLICATION_JSON_VALUE})
    Result<?> queryStoreDetailsOfCompanyMenu(String companyId, String menuId, String storeId,
                                             @RequestParam(value = "mealType", required = false, defaultValue = "lunch") String mealType,
                                             @RequestParam(required = false) String keyword) {

        Result<Object> result = new Result<>();

        log.info("/queryStoreDetailsOfCompanyMenu, companyId: {}, menuId: {}, storeId: {}, mealType: {}, keyword: {}", companyId, menuId, storeId, mealType, keyword);
        // 1. 根据菜单id，店铺id，用餐时段，查询出菜单中，挑选的该店铺的菜品id列; 即配置菜单时，通常只会从店铺中挑选部分菜品，因此用户在小程序上，点击进入店铺后，看到的只是店铺的部分，被挑选配置到菜单上的菜品
        // select food_id from food_menu where menu_id = #{menuId} and store_id = #{storeId} and status = 1
        List<FoodMenu> foodCustomizedInfoOfOneMenu = foodMenuMapper.selectList(
                new LambdaQueryWrapper<FoodMenu>()
                        .eq(FoodMenu::getMenuId, menuId)
                        .eq(FoodMenu::getStoreId, storeId)
                        .eq(FoodMenu::getMealType, mealType)
                        .eq(FoodMenu::getStatus, FoodMenuStatus.ACTIVE.getStatus())
                        .eq(FoodMenu::getDelFlag, false)
                        // 根据更新时间降序排列，保证后续 Collectors.toMap 存在多个 food_menu 记录时以最新的 food_menu 记录为准
                        .orderByDesc(FoodMenu::getUpdateTime)
        );
        List<String> foodIdsOfStore = foodCustomizedInfoOfOneMenu.stream().map(FoodMenu::getFoodId).collect(Collectors.toList());

        // select * from food where id in ();
        // 2. 访问food 表，获取某个店铺中 ，到被挑选到菜单上的菜品的详情，图片，名称，之类的
        List<Food> foodRepositoryList = foodMapper.selectList(new LambdaQueryWrapper<Food>().in(Food::getId, foodIdsOfStore));
        // 相同菜品在不同公司的菜单上的价格，限购份数不同，实际的销售价格需要根据 menuId从 food_menu表获取；menu_id 具有唯一性，不同公司的menu_id，相同公司的不同菜单的 menu_id 都不同，
        // 因此查询时可以直接使用menu_id，不需要同时使用 company_id 和 menu_id
        // 2025/06/02 更新：直接通过 menuId 查询 FoodMenu 记录是有问题的
        // 举例：
        // 当同一个菜品在多个用餐时段都添加到菜单上时，此时仅通过 menuId 查出的 FoodMenu 记录为多条，在通过 240 行 Collectors.toMap 时仅会取其一
        // 导致更新一个用餐时段的数据后，用户小程序要么展示另一个用餐时段的菜品价格，要么多个用餐时段的菜品价格展示为相同的价格
        // 所以直接应该按照 menuId, storeId, mealType, status, delFlag 进行同时过滤，防止出现上述问
        // List<FoodMenu> foodCustomizedInfoOfOneMenu = foodMenuMapper.selectList(new LambdaQueryWrapper<FoodMenu>().eq(FoodMenu::getMenuId, menuId));
        // key为某个公司的菜单中的某个foodId，value为 foodMenu对象，主要是为了获取 foodMenu中的实际销售价格和限购份数；理论上同一个menu_id 的所有菜品项中，不会存在food_id相同的菜品项，
        // 当存在隐藏bug或脏数据时，需要避免 toMap() 方法报错，传递第三个参数，当出现多个相同的food_id时，默认选择第一个，其他的丢弃，避免 toMap() 抛异常
        Map<String, FoodMenu> foodIdFoodMenuItemMap = foodCustomizedInfoOfOneMenu.stream().collect(
                Collectors.toMap(FoodMenu::getFoodId, Function.identity(), (foodMenu1, foodMenu2) -> foodMenu1));
        List<String> mustSelectedFoods = new ArrayList<>();
        // key为菜品类型，value为该类型的菜品列表，比如 key为 "今日特惠"，value为 [{}, {}, {}]
        HashMap<String, List<JSONObject>> groupedFoods = new HashMap<>();
        // 用户只能看到 上架中的菜品，如果商户通过 商户小程序下架了某个菜品，此时用户小程序上无法再看到该菜品
        // 若 商户通过商户小程序，上架了某个之前下架，当前还在公司菜单中的菜品，则用户小程序上可以看到该菜品
        List<Food> onSaleFoodList;
        if (keyword == null || keyword.isEmpty()) {
            // 如果查询关机字为空，返回上架中的菜品
            onSaleFoodList = foodRepositoryList.stream().filter(food -> FoodStatus.ACTIVE.getStatus().equals(food.getStatus())).collect(Collectors.toList());
        } else {
            // 如果查询关键字不为空，则返回上架中且菜品名称包含关键字的菜品列表
            onSaleFoodList = foodRepositoryList.stream()
                    .filter(food -> FoodStatus.ACTIVE.getStatus().equals(food.getStatus()))
                    .filter(food -> food.getName().contains(keyword))
                    .collect(Collectors.toList());
        }

        onSaleFoodList.forEach(food -> {
            // 遍历店铺中各个菜品时，将必选菜品放到列表中
            if (food.getRequired()) {
                mustSelectedFoods.add(food.getId());
            }
            // 将菜品按照 菜品类别进行归类
            String foodTypeName = food.getFoodTypeName();
            List<JSONObject> oneTypeFoods;
            if (groupedFoods.containsKey(foodTypeName)) {
                oneTypeFoods = groupedFoods.get(foodTypeName);
            } else {
                oneTypeFoods = new ArrayList<>();
                groupedFoods.put(foodTypeName, oneTypeFoods);
            }

            JSONObject oneFood = buildFoodInfos(food, foodIdFoodMenuItemMap);
            oneTypeFoods.add(oneFood);
        });

        // select * from store where id = #{storeId}
        Store store = storeMapper.selectById(storeId);
        if (store == null) {

            result.setCode(ResultCode.INTERNAL_SERVER_ERROR);
            result.setMessage("找不到店铺信息，请联系工作人员");
            return result;
        }

        JSONObject resultData = buildStoreInfos(store);
        // 菜品类别列表，并在最前边添加 "全部" 这个类别，用于展示全部菜品
        List<String> foodTypes = new ArrayList<>();
        foodTypes.add("全部");
        // 将包含菜品的菜品类别放入菜品类别列表中，防止前端展示的餐品类别中，点击后不存在菜品
        foodTypes.addAll(groupedFoods.keySet());
        resultData.put("foodTypes", foodTypes);

        // SELECT * FROM menu_store_config
        // WHERE menu_id = #{menuId} AND company_id = #{companyId} AND storeId = #{storeId} ANd del_flag = 0
        // ORDER BY create_time DESC
        // 按时间排序取最新的，防止出现重复数据
        LambdaQueryWrapper<MenuStoreConfig> menuStoreConfigQueryWrapper = new LambdaQueryWrapper<MenuStoreConfig>()
                .eq(MenuStoreConfig::getMenuId, menuId)
                .eq(MenuStoreConfig::getCompanyId, companyId)
                .eq(MenuStoreConfig::getStoreId, storeId)
                // 如果不添加 用餐时段，当某个店铺被配置到 公司的 午餐，晚餐，早餐 时段时，此处的sql会查询出3条记录，后面的 menuStoreConfigs.get(0) 会导致返回结果具有随机性
                .eq(MenuStoreConfig::getMealType, mealType)
                .eq(MenuStoreConfig::getDelFlag, false)
                .orderByDesc(MenuStoreConfig::getCreateTime);

        // 前面的 buildStoreInfos() 方法中，从 store 表中取出 店铺的默认的点餐开始，结束时间，其实是有问题的，在菜单上挑选某个店铺后，可以单独配置该店铺为某个公司供餐时，点餐开始，结束时间，
        // 因此此处的点餐开始，结束时间， 会覆盖 店铺在 store 表中的默认值
        List<MenuStoreConfig> menuStoreConfigs = menuStoreConfigMapper.selectList(menuStoreConfigQueryWrapper);
        if (menuStoreConfigs != null && !menuStoreConfigs.isEmpty()) {
            // 取最新的店铺配置信息
            MenuStoreConfig menuStoreConfig = menuStoreConfigs.get(0);
            resultData.put("businessHoursBegin", menuStoreConfig.getStartTime());
            resultData.put("businessHoursEnd", menuStoreConfig.getEndTime());
        }

        // 存放所有菜品
        List<JSONObject> allFoods = new ArrayList<>();

        for (Map.Entry<String, List<JSONObject>> entry : groupedFoods.entrySet()) {
            List<JSONObject> foodList = entry.getValue();
            allFoods.addAll(foodList);

            // 对每个菜品类别下的菜品按照 sortFlag 升序进行排列
            foodList.sort(Comparator.comparingInt(o -> o.getInteger("foodSortFlag")));
        }

        // 对 "全部" 这个菜品类别下的所有菜品按照 sortFlag 升序进行排列
        allFoods.sort(Comparator.comparingInt(o -> o.getInteger("foodSortFlag")));

        LinkedHashMap<String, List<JSONObject>> finalGroupedFoods = new LinkedHashMap<>();
        // 保证 "全部" 这个类别一定在最前边
        finalGroupedFoods.put("全部", allFoods);
        finalGroupedFoods.putAll(groupedFoods);

        resultData.put("mustSelectedFoods", mustSelectedFoods);
        resultData.put("groupedFoods", finalGroupedFoods);

        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(resultData);
        log.warn("response: {}", result);
        return result;
    }

    private JSONObject buildFoodInfos(Food food, Map<String, FoodMenu> foodIdFoodMenuItemMap) {

        JSONObject oneFood = new JSONObject();

        oneFood.put("foodId", food.getId());
        oneFood.put("foodName", food.getName());
        oneFood.put("image", food.getImage());
        FoodMenu foodMenu = foodIdFoodMenuItemMap.get(food.getId());
        float salePriceInYuan = foodMenu.getSalePrice() * 1.0F / 100;
        oneFood.put("salePrice", salePriceInYuan);
        float showPriceInYuan = foodMenu.getShowPrice() * 1.0F / 100;
        oneFood.put("showPrice", showPriceInYuan);
        oneFood.put("limitCount", foodMenu.getLimitCount());
        oneFood.put("description", food.getDescription());
        oneFood.put("specification", JSONObject.parseArray(food.getSpecification()));
        oneFood.put("foodSortFlag", food.getSortFlag());

        return oneFood;
    }

    private JSONObject buildStoreInfos(Store store) {

        JSONObject storeInfos = new JSONObject();

        storeInfos.put("storeName", store.getName());
        storeInfos.put("storeId", store.getId());

        // 从店铺信息中取出菜品类别，转换成 JSONArray，并在最前边添加 "全部" 这个类别，用于展示全部菜品
        // JSONArray foodTypes = JSONObject.parseArray(store.getFoodTypes());
        // foodTypes.add(0, "全部");
        // storeInfos.put("foodTypes", foodTypes);
        storeInfos.put("sales", 999); // todo 店铺销量，模拟数据，没什么用，可以考虑删除
        storeInfos.put("score", 5.0); // todo 店铺评分，模拟数据，没什么用，可以考虑删除
        storeInfos.put("coupons", new ArrayList<String>()); // todo 优惠券id，没什么用，可以考虑删除
        // 默认使用店铺里定义的时间
        storeInfos.put("businessHoursBegin", store.getBusinessStart());
        storeInfos.put("businessHoursEnd", store.getBusinessEnd());
        storeInfos.put("logo", store.getLogo());
        storeInfos.put("storeDescription", store.getDescription());

        return storeInfos;
    }


    @Operation(summary = "2.4 查看用户购物车中数据", tags = {"2. 用户小程序-下单"})
    @GetMapping(value = "/queryShoppingCart")
    public Result<?> queryShoppingCart(
            HttpServletRequest request,
            @RequestParam("menuId") String menuId,
            @RequestParam("phone") String phone,
            @RequestParam(value = "mealType", required = false, defaultValue = "lunch") String mealType,
            @RequestParam(value = "userId", required = false, defaultValue = "") String userIdForDebug) {
        String requestId = UUID.randomUUID().toString();
        log.info("/queryShoppingCart, requestId: {}, menuId: {}, phone: {}, mealType: {}, userIdForDebug: {}", requestId, menuId, phone, mealType, userIdForDebug);

        String token = request.getHeader("X-Access-Token");
        // String userId = JxUserUtil.getUserId(token, userIdForDebug);

        // 当用户下单后，将购物车中菜品的del_flag 更新成 1，已删除即可
        List<ShoppingCart> historyShoppingCartList = shoppingCartMapper.selectList(
                new LambdaQueryWrapper<ShoppingCart>()
                        .eq(ShoppingCart::getPhone, phone)
                        .eq(ShoppingCart::getMenuId, menuId)
                        .eq(ShoppingCart::getMealType, mealType)
                        .eq(ShoppingCart::getDelFlag, false));

        // 如果购物车中菜品信息不为空，需要过滤掉已经失效的菜品信息，失效菜品定义：
        // 1. 菜品在 food 表中被标记为已下架（status = 2）或已删除（del_flag = 1）
        // 2. 菜品在 shopping_cart 表和 food 表中名称不一致
        // 3. 菜品在 food_menu 表中已被移除（del_flag = 1 或 status = 2）
        if (historyShoppingCartList != null && !historyShoppingCartList.isEmpty()) {
            // 获取购物车中所有的菜品 ID
            List<String> foodIdListInShoppingCart = historyShoppingCartList.stream()
                    .map(ShoppingCart::getFoodId)
                    .collect(Collectors.toList());

            // SELECT * FROM food WHERE id IN (?, ?, ?);
            LambdaQueryWrapper<Food> foodQueryWrapper = new LambdaQueryWrapper<Food>()
                    .in(Food::getId, foodIdListInShoppingCart);
            List<Food> foodList = foodMapper.selectList(foodQueryWrapper);
            // 将 foodList 转换成 Map<foodId, food>，方便后续查找
            Map<String, Food> foodMap = foodList.stream().collect(Collectors.toMap(Food::getId, Function.identity()));

            List<String> storeIdListInShoppingCart = historyShoppingCartList.stream().map(ShoppingCart::getStoreId).collect(Collectors.toList());

            // SELECT * FROM food_menu
            // WHERE menu_id = ? AND meal_type = ? AND store_id IN (?, ?, ?) AND food_id IN (?, ?, ?);
            // 之所以 store_id 使用 IN 查询，是因为考虑到一个用户可能同时下单多个店铺的菜品
            LambdaQueryWrapper<FoodMenu> foodMenuQueryWrapper = new LambdaQueryWrapper<FoodMenu>()
                    .eq(FoodMenu::getMenuId, menuId)
                    .eq(FoodMenu::getMealType, mealType)
                    .in(FoodMenu::getStoreId, storeIdListInShoppingCart)
                    .in(FoodMenu::getFoodId, foodIdListInShoppingCart);
            List<FoodMenu> foodMenuList = foodMenuMapper.selectList(foodMenuQueryWrapper);
            // 将 foodMenuList 转换成 Map<foodId, foodMenu>，方便后续查找
            // 由于可能存在多个 menu_id, meal_type, food_id, store_id 相同，但仅有一个 del_flag = 0 的记录
            // 所以不能简单使用 foodId 将 foodMenuList 转换成 Map<foodId, foodMenu>，这样会导致 java.lang.IllegalStateException: Duplicate key 异常
            // 例如：
            // SELECT * FROM food_menu WHERE menu_id = '52bec7f9a5cf425a8ef9eaaba124383a' AND food_id = 'c952804d0cd410267f404dd3f0451234' AND store_id = 'c852804d0cd410267f404dd3f0451234' AND meal_type = 'dinner'
            // 2fb8335548a58c18c715c61580c27710	bd1628b9ad64b369bd6580f72aebdcae	2025-01-15 12:25:18	1	c852804d0cd410267f404dd3f0451234	c952804d0cd410267f404dd3f0451234	2000	1350	0	999	0	2025-01-15 12:25:18	2025-01-15 00:00:00	52bec7f9a5cf425a8ef9eaaba124383a	3	dinner
            // f8b22578ed23076f7146f28bb048a87b	bd1628b9ad64b369bd6580f72aebdcae	2024-11-17 19:07:43	1	c852804d0cd410267f404dd3f0451234	c952804d0cd410267f404dd3f0451234	2000	1350	0	999	1	2024-11-17 19:07:43	2024-12-15 22:05:42	52bec7f9a5cf425a8ef9eaaba124383a	3	dinner	55880e47d8bf9ce642424bce592c7339
            // 所以直接使用 food_id 进行分组，然后得到的 List<FoodMenu> 中，只要有一个 del_flag = 0 且 status = 1 的记录，即说明该菜品在 food_menu 表中未被移除
            Map<String, List<FoodMenu>> foodMenuMap = foodMenuList.stream().collect(Collectors.groupingBy(FoodMenu::getFoodId));

            // 根据失效菜品定义，对购物车中的菜品进行过滤
            List<ShoppingCart> validList = historyShoppingCartList.stream()
                    // 过滤名称不一致或者在 food 表中已失效的数据
                    .filter(history -> {
                        Food food = foodMap.get(history.getFoodId());
                        if (food == null) {
                            // 如果购物车中的菜品 ID 在菜品表中不存在，直接过滤掉
                            log.info("/queryShoppingCart, requestId: {}, shoppingCartId: {}, foodId: {}, foodName: {}, 在菜品表中不存在",
                                    requestId, history.getId(), history.getFoodId(), history.getFoodName());
                            return false;
                        }

                        if (!food.getName().trim().equals(history.getFoodName().trim())) {
                            // 如果购物车中的菜品名称和菜品表中的菜品名称不一致，过滤掉
                            log.info("/queryShoppingCart, requestId: {}, shoppingCartId: {}, foodId: {}, foodName: {}, 在菜品表中名称已修改",
                                    requestId, history.getId(), history.getFoodId(), history.getFoodName());
                            return false;
                        }

                        if (food.getDelFlag() || FoodStatus.DEACTIVATE.getStatus().equals(food.getStatus())) {
                            // 如果菜品在 food 表中被标记为已下架（status = 2）或已删除（del_flag = 1），过滤掉
                            log.info("/queryShoppingCart, requestId: {}, shoppingCartId: {}, foodId: {}, foodName: {}, 在菜品表中已下架或已删除",
                                    requestId, history.getId(), history.getFoodId(), history.getFoodName());
                            return false;
                        }

                        // 菜品未被删除，并且菜品状态为 "上架" 状态，保留
                        return true;
                    })
                    // 过滤在 food_menu 表中已失效的数据
                    .filter(history -> {
                        List<FoodMenu> foodMenus = foodMenuMap.get(history.getFoodId());
                        if (foodMenus == null || foodMenus.isEmpty()) {
                            // 如果购物车中的菜品 ID 在菜品菜单表中不存在，直接过滤掉
                            log.info("/queryShoppingCart, requestId: {}, shoppingCartId: {}, foodId: {}, foodName: {}, 在菜品菜单表中不存在",
                                    requestId, history.getId(), history.getFoodId(), history.getFoodName());
                            return false;
                        }

                        for (FoodMenu foodMenu : foodMenus) {
                            // 相同的菜品 ID，只要有一个菜品在 food_menu 中 del_flag 标记为 "未删除" 且 菜单状态 status 为 "上架" 状态，即说明该菜品在 food_menu 表中未被移除
                            if (!foodMenu.getDelFlag() && FoodMenuStatus.ACTIVE.getStatus().equals(foodMenu.getStatus())) {
                                return true;
                            }
                        }

                        // 如果所有的 food_menu 记录都已被删除或者已下架，则过滤掉
                        log.info("/queryShoppingCart, requestId: {}, shoppingCartId: {}, foodId: {}, foodName: {}, 在菜品菜单表中已下架或已删除",
                                requestId, history.getId(), history.getFoodId(), history.getFoodName());
                        return false;
                    })
                    .collect(Collectors.toList());

            if (validList.size() != historyShoppingCartList.size()) {
                // 如果过滤后的数据量小于原始数据量，说明有部分数据被过滤掉了，需要在购物车表中清除这些数据

                // 找出已经失效的购物车菜品 ID 列表
                Set<String> validFoodIds = validList.stream()
                        .map(ShoppingCart::getFoodId)
                        .collect(Collectors.toSet());
                List<String> invalidShoppingCartIdList = historyShoppingCartList.stream()
                        .filter(history -> !validFoodIds.contains(history.getFoodId()))
                        .map(ShoppingCart::getId)
                        .collect(Collectors.toList());
                log.info("/queryShoppingCart, requestId: {}, invalidShoppingCartIdList: {}, 购物车中存在已失效的菜品，需要删除", requestId, invalidShoppingCartIdList);

                // 更新数据库，将已失效的购物车菜品标记为已删除
                LambdaUpdateWrapper<ShoppingCart> updateWrapper = new LambdaUpdateWrapper<ShoppingCart>()
                        .eq(ShoppingCart::getDelFlag, false)
                        .in(ShoppingCart::getId, invalidShoppingCartIdList);
                ShoppingCart shoppingCart = new ShoppingCart();
                shoppingCart.setDelFlag(true);
                int updateCount = shoppingCartMapper.update(shoppingCart, updateWrapper);
                if (updateCount < 0) {
                    log.error("/queryShoppingCart, requestId: {}, 删除购物车中已失效的菜品失败，updateCount: {}", requestId, updateCount);
                }

                // 将历史购物车列表替换成合法的购物车列表，以便于不改动后续逻辑
                historyShoppingCartList = validList;
            }
        }

        List<HashMap<String, Object>> formatedFoodList = new ArrayList<>();
        if (historyShoppingCartList != null) {
            for (ShoppingCart shoppingCartFoodDo : historyShoppingCartList) {
                HashMap<String, Object> formatedData = new HashMap<>();
                String foodId = shoppingCartFoodDo.getFoodId();
                formatedData.put("shoppingCartId", shoppingCartFoodDo.getId());
                formatedData.put("foodId", foodId);
                formatedData.put("foodName", shoppingCartFoodDo.getFoodName());
                formatedData.put("storeId", shoppingCartFoodDo.getStoreId());
                formatedData.put("storeName", shoppingCartFoodDo.getStoreName());
                // 返回用户选中的规格，用户添加菜品到购物车时，会传递那些规格被选中了，当查询时，返回添加购物车时，传递的规格即可
                formatedData.put("specifications", JSONArray.parseArray(shoppingCartFoodDo.getSpecification()));
                formatedData.put("count", shoppingCartFoodDo.getCount());
                formatedData.put("foodImage", shoppingCartFoodDo.getFoodImage());

                double foodPrice = 1.0 * shoppingCartFoodDo.getSalePrice() / shoppingCartFoodDo.getCount() / 100;
                formatedData.put("foodPrice", foodPrice);

                double showPrice = 1.0 * shoppingCartFoodDo.getShowPrice() / 100;
                formatedData.put("showPrice", showPrice);

                formatedData.put("totalPrice", shoppingCartFoodDo.getSalePriceInYuan());

                formatedFoodList.add(formatedData);
            }
        } else {
            log.error("菜单id: {}, 用户id: {} 购物车中的商品为空", menuId, phone);
        }

        Result<Object> result = new Result<>();
        result.setCode(ResultCode.SUCCESS);
        result.setMessage("success");
        result.setData(formatedFoodList);

        return result;
    }


    /**
     * specification=[{"name":"辣子鸡","type":"菜品一"},{"name":"辣子鸡","type":"菜品二"}]
     * 同类菜品添加合并功能
     *
     * @return
     */
    @Operation(summary = "2.5 添加菜品到购物车", tags = {"2. 用户小程序-下单"})
    @PostMapping(value = "/addShoppingCart")
    public Result<?> addShoppingCart(@RequestBody JSONObject requestBody) {

        String phone = requestBody.getString("phone");
        String menuId = requestBody.getString("menuId");
        String storeId = requestBody.getString("storeId");
        String foodId = requestBody.getString("foodId");
        String mealType = requestBody.getString("mealType");

        // TODO: 默认查询 lunch，仅作测试用，待删除
        if (mealType == null || mealType.isEmpty()) {
            mealType = "lunch";
        }

        // 前端未传递 specification 字段时，默认设置为 []
        JSONArray selectedSpecifications = requestBody.getJSONArray("specification") == null ? JSONArray.parseArray("[]") : requestBody.getJSONArray("specification");
        Integer count = (Integer) requestBody.get("count");

        log.info("/addShoppingCart, phone: {}, menuId: {}, storeId: {}, foodId: {}, mealType: {}, specification: {}, count: {}",
                phone, menuId, storeId, foodId, mealType, requestBody.getJSONArray("specification"), count);

        // 根据电话号码和菜单 ID 查询出未删除的最新创建的员工信息，使用 selectList 而不是 selectOne 避免查询出多条用户数据造成异常
        LambdaQueryWrapper<User> userQueryWrapper = new LambdaQueryWrapper<>();
        userQueryWrapper.eq(User::getPhoneNumber, phone)
                // 注释掉对 menuId 的查询，因为当员工不在本公司点餐，而是再其它可点餐公司点餐时，前端传递过来的 menuId 就和 user 表中保存的 menuId 不一致，导致查询不到数据
                // user 表中保存的 menuId 是员工所属公司当前配置的菜单 ID
                // .eq(User::getMenuId, menuId)
                .eq(User::getDelFlag, false)
                .orderByDesc(User::getCreateTime);
        Optional<User> userOptional = userMapper.selectList(userQueryWrapper).stream().findFirst();
        if (!userOptional.isPresent()) {
            throw new IllegalArgumentException("手机号为 " + phone + " 的员工不存在，请联系管理员");
        }

        User user = userOptional.get();
        // 获取该用户每餐的限购数量
        int limitCountPerMeal = user.getLimitCount();

        // 查询出当前用户是否有状态为 "待发货" 或 "待收货" 的订单
        List<OrderDetail> orderDetailListForDebug = new ArrayList<>();
        int orderCount = countOrderWithToSendOrToReceive(user.getId(), user.getCompanyId(), mealType, orderDetailListForDebug);
        log.info("phone: {}, menuId: {}, 当前状态为 \"待发货\" 或 \"待收货\" 的订单数量为: {}, 每餐限购份数为: {}", phone, menuId, orderCount, limitCountPerMeal);
        if (orderCount > 0) {
            // 如果存在状态为 "待发货" 或 "待收货" 的订单
            if (orderCount == limitCountPerMeal) {
                log.error("limitCountCheckFailed, 用户已经存在 待发货 或 待收货的 订单，{}", orderDetailListForDebug);
                // 如果当前用户订单为 "待发货" 或 "待收货" 的订单份数 = 每餐限购份数，提示 "已达限购份数"，并且不能继续点餐
                return ResultGenerator.genFailResult("已达限购份数，不能继续点餐");
            }
            // 如果当前用户订单为 "待发货" 或 "待收货" 的订单份数 < 每餐限购份数，继续以下流程
        }

        // 如果不存在状态为 "待发货" 或 "待收货" 的订单，继续以下流程

        // 查询当前购物车中数据，用户添加到购物车中的餐品份数，也需要符合限购条件，如果每餐限购1份，则每餐只能向购物车中添加一份餐品
        List<ShoppingCart> shoppingCarts = listFoodInCart(phone, menuId, mealType);
        if (shoppingCarts.isEmpty()) {
            // 如果当前购物车中没有菜品，可以向购物车中添加菜品
            return addFoodToCart(phone, menuId, storeId, foodId, selectedSpecifications, count, user.getId(), mealType);
        } else {
            // 获取购物车中已有餐品所属的店铺 ID 列表
            List<String> storeIdsInShoppingCart = shoppingCarts.stream()
                    .map(ShoppingCart::getStoreId)
                    .distinct()
                    .collect(Collectors.toList());

            if (storeIdsInShoppingCart.contains(storeId)) {
                // 如果在已经点过菜品的店铺继续新增菜品，需要判断新增的菜品是否为小碗菜
                // 因为小碗菜需要判断同一家店铺小碗菜的数量是否超过了 6 个

                // 查询店铺信息判断是否为 "小碗菜" 店铺
                Store store = storeMapper.selectById(storeId);
                if (store == null) {
                    throw new IllegalArgumentException("ID 为 " + storeId + " 的店铺不存在，请联系管理员");
                }
                Integer mergeFoodItems = store.getMergeFoodItems();
                log.info("storeId: {}, merge_food_items: {}, 是否为小碗菜店铺: {}", storeId, mergeFoodItems, mergeFoodItems == 1);

                if (mergeFoodItems == 1) {
                    // 如果是新增的菜品依旧是小碗菜，查询出购物车中该小碗菜店铺中已经点了多少个小碗菜
                    int mergeFoodCount = shoppingCarts.stream()
                            .filter(shoppingCart -> shoppingCart.getStoreId().equals(storeId))
                            .map(ShoppingCart::getCount)
                            .reduce(0, Integer::sum);
                    log.info("phone: {}, storeId: {}, 当前点餐小碗菜份数: {}", phone, storeId, mergeFoodCount);
                    if (mergeFoodCount >= 6) {
                        // 如果在同一家店铺已经点了 6 份小碗菜，则不能继续点餐
                        return ResultGenerator.genFailResult("同一家店铺的自选套餐，包含米饭最多只能选 6 样");
                    } else {
                        // 小于 6 份小碗菜，则可以继续将小碗菜添加到购物车中
                        return addFoodToCart(phone, menuId, storeId, foodId, selectedSpecifications, count, user.getId(), mealType);
                    }
                }
                // 如果新增的菜品不是小碗菜，继续以下流程
            }

            // 如果在一家新的店铺添加菜品
            // 则根据份数计算规则判断购物车中已有菜品份数 + 状态为 "待发货" 或 "待收货" 的订单份数是否大于等于每餐限购份数
            long foodCount = calculateFoodCount(shoppingCarts, storeIdsInShoppingCart);
            log.info("phone: {}, storeId: {}, 当前点餐份数: {}", phone, storeId, foodCount);
            if (foodCount + orderCount >= limitCountPerMeal) {
                // 如果购物车中已有菜品份数 + 状态为 "待发货" 或 "待收货" 的订单份数大于等于每餐限购份数
                // 则不能继续点餐
                log.error("limitCountCheckFailed, 用户添加到购物车中的菜品份数也需要做选购校验，只能添加不超过限购份数的餐品到购物车，当前购物车中已添加了菜品: {}", storeIdsInShoppingCart);
                return ResultGenerator.genFailResult("已达限购份数，不能继续点餐");
            }
            // 否则，可以向购物车中添加菜品
            return addFoodToCart(phone, menuId, storeId, foodId, selectedSpecifications, count, user.getId(), mealType);
        }
    }

    /**
     * 根据用户 ID 和用户所在公司 ID 查询出订单状态为 "待发货" 或 "待收货" 的订单数量
     *
     * @param userId                  用户 ID
     * @param companyId               用户所在公司 ID
     * @param mealType                那个用餐时段，午餐或晚餐
     * @param orderDetailListForDebug 用于调试
     * @return "待发货" 或 "待收货" 的订单数量
     */
    private int countOrderWithToSendOrToReceive(String userId, String companyId, String mealType, List<OrderDetail> orderDetailListForDebug) {
        // 不能使用 order_payment 表，因为同一次“提交订单”，不管添加了多少菜品，在 order_payment 表中都是同一笔订单
        // 应该使用 order_detail 表，再根据 store_id 分组后根据小碗菜、盒饭份数计算规则来统计
        // LambdaQueryWrapper<OrderPayment> orderPaymentQueryWrapper = new LambdaQueryWrapper<>();
        // orderPaymentQueryWrapper.eq(OrderPayment::getUserId, userId)
        //        .eq(OrderPayment::getCompanyId, companyId)
        //        .in(OrderPayment::getStatus, OrderPaymentStatus.TO_SEND.getValue(), OrderPaymentStatus.TO_RECEIVE.getValue());

        LambdaQueryWrapper<OrderDetail> orderDetailQueryWrapper = new LambdaQueryWrapper<>();
        orderDetailQueryWrapper.eq(OrderDetail::getUserId, userId)
                .eq(OrderDetail::getCompanyId, companyId)
                .eq(OrderDetail::getMealType, mealType)
                .in(OrderDetail::getStatus, OrderPaymentStatus.TO_SEND.getValue(), OrderPaymentStatus.TO_RECEIVE.getValue());

        List<OrderDetail> orderDetailList = orderDetailMapper.selectList(orderDetailQueryWrapper);
        orderDetailListForDebug.addAll(orderDetailList);
        if (orderDetailList == null || orderDetailList.isEmpty()) {
            return 0;
        }

        List<String> storeIds = orderDetailList.stream()
                .map(OrderDetail::getStoreId)
                .distinct()
                .collect(Collectors.toList());

        LambdaQueryWrapper<Store> storeQueryWrapper = new LambdaQueryWrapper<>();
        storeQueryWrapper.in(Store::getId, storeIds);

        List<Store> storeList = storeMapper.selectList(storeQueryWrapper);

        int sum = 0;
        for (Store store : storeList) {
            if (store.getMergeFoodItems() == 0) {
                // 找出不是小碗菜的店铺，并累加在该店铺点餐的份数
                sum += orderDetailList.stream()
                        .filter(orderDetail -> orderDetail.getStoreId().equals(store.getId()))
                        .map(OrderDetail::getQuantity)
                        .reduce(0, Integer::sum);
            } else if (store.getMergeFoodItems() == 1) {
                // 如果是小碗菜店铺，则该店铺中的菜品按 1 份计算
                sum += 1;
            }
        }

        return sum;
    }

    /**
     * 根据用户手机号, 菜单id 查询当前用户购物车中的菜品列表
     * 用户A会从 公司1 移到 公司2，用户A之前在 公司1时，某一天某一顿添加了购物车，但是没有下单，当天被移动到公司2后，添加菜品到购物车时，会出现限购无法下单问题
     * 由于添加购物车接口入参没有传递 公司id，因此 可以使用 menuId 来进行区分，公司1，公司2 的菜单id不同，因此，效果等同于 公司id;
     * 添加购物车限购规则，某一天(dateFlag)，某个用餐时段，某个菜单 上选购的菜品份数，不能超过 限购份数；当前 没有dateFlag 限定条件，基本上也不会出现限购误判
     *
     * @param phone 手机号
     * @return 购物车中的菜品列表
     */
    private List<ShoppingCart> listFoodInCart(String phone, String menuId, String mealType) {
        LambdaQueryWrapper<ShoppingCart> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(ShoppingCart::getPhone, phone)
                .eq(ShoppingCart::getMealType, mealType)
                .eq(ShoppingCart::getMenuId, menuId)
                .eq(ShoppingCart::getDelFlag, false);

        return shoppingCartMapper.selectList(wrapper);
    }

    /**
     * 根据购物车中的已有菜品数据以及去重后的店铺 ID 列表，计算菜品项。
     * 计算规则：若菜品来自merge_food_items为1 菜品项，全部合并成1份；为0的菜品项，每个算1份，即使来自同一个店铺
     * 举例：当前购物车共 10个菜品项，3个菜品项（一荤一素一碗米饭）来自小碗菜店铺1, 4个来自小碗菜2（两荤一素一碗米饭），另外3份都是 牛肉减脂套餐，则 结果为 5 份菜品
     *
     * @param shoppingCarts          购物车中的已有菜品数据
     * @param storeIdsInShoppingCart 去重后的店铺 ID 列表
     * @return 菜品项数量
     */
    private int calculateFoodCount(List<ShoppingCart> shoppingCarts, List<String> storeIdsInShoppingCart) {
        LambdaQueryWrapper<Store> storeQueryWrapper = new LambdaQueryWrapper<>();
        storeQueryWrapper.in(Store::getId, storeIdsInShoppingCart);

        List<Store> storeList = storeMapper.selectList(storeQueryWrapper);

        int sum = 0;
        for (Store store : storeList) {
            if (store.getMergeFoodItems() == 0) {
                // 找出不是小碗菜的店铺，并累加在该店铺点餐的份数
                sum += shoppingCarts.stream()
                        .filter(shoppingCart -> shoppingCart.getStoreId().equals(store.getId()))
                        .map(ShoppingCart::getCount)
                        .reduce(0, Integer::sum);
            } else if (store.getMergeFoodItems() == 1) {
                // 如果是小碗菜店铺，则该店铺中的菜品按 1 份计算
                sum += 1;
            }
        }

        return sum;
    }

    private Result<?> addFoodToCart(String phone, String menuId, String storeId, String foodId, JSONArray
            selectedSpecifications, Integer count, String userId, String mealType) {
        ShoppingCart shoppingCartItem = new ShoppingCart();
        shoppingCartItem.setUserId(userId);
        shoppingCartItem.setPhone(phone);
        shoppingCartItem.setMenuId(menuId);
        shoppingCartItem.setStoreId(storeId);
        shoppingCartItem.setSpecification(selectedSpecifications.toJSONString());
        shoppingCartItem.setCount(count);
        shoppingCartItem.setFoodId(foodId);
        shoppingCartItem.setMealType(mealType);
        // 菜单上的某一个菜品的配置信息，需要 menuId，foodId， mealType 三个唯一定位，如果没有 mealType，若某个菜品被添加到 午餐 和 晚餐 时段，则会导致此处的sql查询出2条记录，而且配置的销售价不同，导致下面 list.get(0) 返回的值具有随机性
        List<HashMap<String, Object>> list = foodMenuMapper.queryFoodDetails(menuId, foodId, mealType);

        if (list == null || list.isEmpty()) {
            String errMsg = MessageFormat.format("添加购物车失败，food menu 中查找不到 menu id: {0}, food id: {1} 的记录", menuId, foodId);
            log.error(errMsg);

            return Result.error(errMsg);
        } else {
            HashMap<String, Object> foodDetails = list.get(0);
            Integer salePrice = (Integer) foodDetails.get("salePrice");
            Integer showPrice = (Integer) foodDetails.get("showPrice");
            shoppingCartItem.setSalePrice(salePrice);
            shoppingCartItem.setShowPrice(showPrice);
            String foodName = (String) foodDetails.get("foodName");
            shoppingCartItem.setFoodName(foodName);
            shoppingCartItem.setFoodImage((String) foodDetails.get("foodImage"));
            shoppingCartItem.setStoreName((String) foodDetails.get("storeName"));

            LambdaUpdateWrapper<ShoppingCart> updateWrapper = new LambdaUpdateWrapper<ShoppingCart>()
                    .eq(ShoppingCart::getPhone, phone)
                    .eq(ShoppingCart::getMealType, mealType)
                    .eq(ShoppingCart::getDelFlag, false);

            LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<ShoppingCart>()
                    .eq(ShoppingCart::getPhone, phone)
                    .eq(ShoppingCart::getMealType, mealType)
                    .eq(ShoppingCart::getDelFlag, false);
            String foodPrintName = null;

            if (selectedSpecifications.isEmpty()) {
                // 当菜品的规格列表为空数组时，表示不是自选套餐，而是 盒饭，最终打印在标签上的菜品名就是 foodName，仅需要根据foodId判断是否为相同菜品，是移除菜品项，还是仅仅修改份数
                queryWrapper.eq(ShoppingCart::getFoodId, foodId);
                updateWrapper.eq(ShoppingCart::getFoodId, foodId);
                foodPrintName = foodName;
            } else {
                foodPrintName = jxOrderServiceImpl.buildFoodPrintNameFromSpecification(selectedSpecifications);
                queryWrapper.eq(ShoppingCart::getFoodId, foodId);
                queryWrapper.eq(ShoppingCart::getFoodPrintName, foodPrintName); // 根据最终打印在标签纸上的菜品名来判断 是否已经有同种类型的菜品，是新增记录还是 仅增加份数

                updateWrapper.eq(ShoppingCart::getFoodId, foodId);
                updateWrapper.eq(ShoppingCart::getFoodPrintName, foodPrintName); // 根据最终打印在标签纸上的菜品名来判断 是否已经有同种类型的菜品，是新增记录还是 仅增加份数
            }
            shoppingCartItem.setFoodPrintName(foodPrintName);

            List<ShoppingCart> list2 = shoppingCartMapper.selectList(queryWrapper);
            if (list2 != null && !list2.isEmpty()) {
                ShoppingCart jxShoppingCartFoodDo = list2.get(0);
                // 购物车表中已经存在该菜品，再次添加，只需要将该记录的数量加1，总价格加一倍
                int oneFoodPrice = jxShoppingCartFoodDo.getSalePrice() / jxShoppingCartFoodDo.getCount();
                int newCount = jxShoppingCartFoodDo.getCount() + 1;
                int totalSalePrice = newCount * oneFoodPrice;
                int updatedCount = shoppingCartMapper.update(updateWrapper.set(ShoppingCart::getCount, newCount).set(ShoppingCart::getSalePrice, totalSalePrice));
                if (updatedCount == 1) {
                    log.info("更新购物车中该菜品的份数成功");
                    return Result.ok("从购物车中减少菜品成功");
                } else {
                    return Result.error("从购物车移除菜品失败，请联系后台技术人员进行处理");
                }
            } else {
                // 向表中新增一条记录
                // 这两个字段在新增数据时，已经配置了 MyBatis Plus 会自动赋值（见：UpdateRelatedFieldsMetaHandler）
                // shoppingCartItem.setCreateTime(new Date());
                // shoppingCartItem.setUpdateTime(new Date());
                int insertCount = shoppingCartMapper.insert(shoppingCartItem);
                if (insertCount == 1) {
                    log.info("成功向购物车中添加菜品");
                    return Result.ok("成功向购物车中添加菜品");
                } else {
                    log.info("向购物车中添加菜品失败");
                    return Result.error("向购物车中添加菜品失败，请联系后台技术人员进行处理");
                }
            }
        }
    }


    /**
     * specification=[{"name":"辣子鸡","type":"菜品一"},{"name":"辣子鸡","type":"菜品二"}]
     *
     * @return
     */
    @Operation(summary = "2.9 移除购物车中某个菜品", tags = {"2. 用户小程序-下单"})
    @DeleteMapping(value = "/removeShoppingCart")
    public Result<?> removeShoppingCart(@RequestParam("phone") String phone,
                                        @RequestParam("foodId") String foodId,
                                        @RequestParam(value = "specification", required = false, defaultValue = "[]") String specificationStr,
                                        @RequestParam("count") int count,
                                        @RequestParam(value = "mealType", required = false, defaultValue = "lunch") String mealType) {

        log.info("/removeShoppingCart, phone: {}, foodId: {}, specification: {}, count: {}, mealType: {}", phone, foodId, specificationStr, count, mealType);

        LambdaUpdateWrapper<ShoppingCart> updateWrapper = new LambdaUpdateWrapper<ShoppingCart>()
                .eq(ShoppingCart::getPhone, phone)
                .eq(ShoppingCart::getMealType, mealType)
                .eq(ShoppingCart::getDelFlag, false);

        LambdaQueryWrapper<ShoppingCart> queryWrapper = new LambdaQueryWrapper<ShoppingCart>()
                .eq(ShoppingCart::getPhone, phone)
                .eq(ShoppingCart::getMealType, mealType)
                .eq(ShoppingCart::getDelFlag, false);
        String foodPrintName = null;
        // 兼容参数异常，当某些菜品没有规格时，点击店铺菜品 - 号，从购物车中移除菜品时，会传递 undefined，导致 解析json字符串报错
        JSONArray specification = new JSONArray();
        if (!StringUtils.isBlank(specificationStr) && specificationStr.trim().startsWith("[")) {
            specification = JSONArray.parseArray(specificationStr);
        }

        if (specification.isEmpty()) {
            // 当菜品的规格列表为空数组时，表示不是自选套餐，而是 盒饭，最终打印在标签上的菜品名就是 foodName，仅需要根据foodId判断是否为相同菜品，是移除菜品项，还是仅仅修改份数
            queryWrapper.eq(ShoppingCart::getFoodId, foodId);
            updateWrapper.eq(ShoppingCart::getFoodId, foodId);
        } else {
            foodPrintName = jxOrderServiceImpl.buildFoodPrintNameFromSpecification(specification);
            queryWrapper.eq(ShoppingCart::getFoodId, foodId);
            queryWrapper.eq(ShoppingCart::getFoodPrintName, foodPrintName); // 根据最终打印在标签纸上的菜品名来判断 是否已经有同种类型的菜品，是新增记录还是 仅增加份数

            updateWrapper.eq(ShoppingCart::getFoodId, foodId);
            updateWrapper.eq(ShoppingCart::getFoodPrintName, foodPrintName); // 根据最终打印在标签纸上的菜品名来判断 是否已经有同种类型的菜品，是新增记录还是 仅增加份数
        }

        List<ShoppingCart> list = shoppingCartMapper.selectList(queryWrapper);
        if (list != null && list.size() > 0) {
            ShoppingCart jxShoppingCartFoodDo = list.get(0);
            if (jxShoppingCartFoodDo.getCount() > 1) {
                int oneFoodPrice = jxShoppingCartFoodDo.getSalePrice() / jxShoppingCartFoodDo.getCount();
                int newCount = jxShoppingCartFoodDo.getCount() - count;
                int totalSalePrice = newCount * oneFoodPrice;
                int updatedCount = shoppingCartMapper.update(updateWrapper.set(ShoppingCart::getCount, newCount).set(ShoppingCart::getSalePrice, totalSalePrice));
                if (updatedCount == 1) {
                    log.info("更新购物车中该菜品的份数成功");
                    return Result.ok("从购物车中减少菜品成功");
                } else {
                    return Result.error("从购物车移除菜品失败，请联系后台技术人员进行处理");
                }
            } else {
                // 只是标记删除，将删除标志 del_flag 置为 1
                int deleteCount = shoppingCartMapper.update(updateWrapper.set(ShoppingCart::getDelFlag, true));
                if (deleteCount == 1) {
                    log.info("成功从购物车移除菜品，仅标记删除");
                    return Result.ok("将菜品从购物车删除成功");
                } else {
                    log.info("从购物车移除菜品失败");
                    return Result.error("从购物车移除菜品失败，请联系后台技术人员进行处理");
                }
            }
        } else {
            log.error("购物车表中无此用户id：{}, 菜品id：{}, mealType: {} 的菜品，无法删除购物车中的该菜品", phone, foodId, mealType);
            return Result.ok();
        }
    }


    /**
     * 通过用户手机号查询该用户可点餐的公司列表，默认选中该用户所属公司
     *
     * @param phone 用户手机号
     */
    @GetMapping("/queryCanOrderFoodCompanyListByPhone")
    public Result<List<UserCanOrderFoodCompanyRespVo>> queryCanOrderFoodCompanyListByPhone(@RequestParam("phone") String phone) {
        String requestId = UUID.randomUUID().toString();
        log.info("requestId {}, employee mini app view, /queryCanOrderFoodCompanyListByPhone, phone: {}", requestId, phone);
        LambdaQueryWrapper<User> userQueryWrapper = new LambdaQueryWrapper<User>()
                .eq(User::getPhoneNumber, phone)
                .eq(User::getDelFlag, false);
        User user = userMapper.selectOne(userQueryWrapper);

        // 用户所属公司 ID
        String companyId = user.getCompanyId();
        String departmentId = user.getDepartmentId();
        String employeeId = user.getId();

        List<UserCanOrderFoodCompanyRespVo> voList = getUserCanOrderFoodCompanyRespVoList(companyId, departmentId, employeeId);
        log.info("requestId {}, employee mini app view, /queryCanOrderFoodCompanyListByPhone, response: {}", requestId, JSONObject.toJSONString(voList));
        return ResultGenerator.genSuccessResult(voList);
    }

    private List<UserCanOrderFoodCompanyRespVo> getUserCanOrderFoodCompanyRespVoList(String companyId, String departmentId, String employeeId) {
        Company company = companyMapper.selectById(companyId);
        UserCanOrderFoodCompanyRespVo selectedVo = new UserCanOrderFoodCompanyRespVo();
        selectedVo.setCompanyId(companyId);
        selectedVo.setCompanyName(company.getName());
        selectedVo.setDepartmentId(departmentId);
        selectedVo.setSelected(true);

        LambdaQueryWrapper<UserOtherCompRelation> userOtherCompRelationQueryWrapper = new LambdaQueryWrapper<UserOtherCompRelation>()
                .eq(UserOtherCompRelation::getUserId, employeeId)
                .eq(UserOtherCompRelation::getDelFlag, false);
        List<UserOtherCompRelation> userOtherCompRelationList = userOtherCompRelationMapper.selectList(userOtherCompRelationQueryWrapper);
        if (CollectionUtils.isEmpty(userOtherCompRelationList)) {
            // 如果该员工没有其它可点餐公司，直接返回该用户所属公司信息即可
            return Collections.singletonList(selectedVo);
        }

        List<String> otherCompanyIdList = userOtherCompRelationList.stream().map(UserOtherCompRelation::getOtherCompanyId).collect(Collectors.toList());
        List<Company> otherCompanyList = companyMapper.selectBatchIds(otherCompanyIdList);
        Map<String, String> companyIdToNameMap = otherCompanyList.stream().collect(Collectors.toMap(Company::getId, Company::getName));

        List<UserCanOrderFoodCompanyRespVo> voList = new ArrayList<>(userOtherCompRelationList.size() + 1);
        voList.add(selectedVo);
        for (UserOtherCompRelation userOtherCompRelation : userOtherCompRelationList) {
            UserCanOrderFoodCompanyRespVo vo = new UserCanOrderFoodCompanyRespVo();
            vo.setCompanyId(userOtherCompRelation.getOtherCompanyId());
            vo.setCompanyName(companyIdToNameMap.getOrDefault(userOtherCompRelation.getOtherCompanyId(), "未知的公司名称"));
            vo.setDepartmentId(userOtherCompRelation.getDepartmentId());
            vo.setSelected(false);
            voList.add(vo);
        }

        return voList;
    }
}
